#!/usr/bin/env bash

# ===========================================================================
# File: "ralpha"
#                        Created: 2017-03-21 07:55:09
#              Last modification: 2017-04-06 15:33:19
# Author: Bernard Desgraupes
# e-mail: <bdesgraupes@users.sourceforge.net>
# Copyright (c) 2017 Alpha Cabal
# All rights reserved.
# This file is part of the AlphaCocoa project.
# ===========================================================================

VERSION_NUMBER="0.1"

# Initialization
ALF_HOST=
ALF_DEFAULT_HOST=localhost
ALF_PORT=
ALF_DEFAULT_PORT=51954
ALF_ENC=utf-8
ALF_FILE_PATH=""
ALF_SELECTION=""
ALF_MODE=""
ALF_VERBOSE=0
ALF_DEBUG_INFO=0
ALF_WAIT=0
ALF_FORCE=0
ALF_NEW=0
ALF_READ_STRING=
ALF_READ_STDIN=0
ALF_STARTTEXT=`printf '\02'`
ALF_ENDTEXT=`printf '\03'`
ALF_EOT=`printf '\04'`

function status_log {
    if [ $ALF_VERBOSE == 1 ]; then
        echo "$@" 1>&2
    fi
}

function status_debug {
    if [ $ALF_DEBUG_INFO == 1 ]; then
        echo "$1" 1>&2
    fi
}

print_usage () {
	echo "Usage: "
    echo "    " $(basename $0) "[options] ?linespec? ?--? file" 
    echo "    " $(basename $0) "[options] ?linespec? -c ?--? name [string|-]" 
    echo "    " $(basename $0) "[-h|-v]" 
    echo "Options:"
    echo "    -c        create a new file"
    echo "    -d        print debugging information"
    echo "    -e enc    encoding (default UTF-8)"
    echo "    -f        force open (if not writable)"
    echo "    -h        print usage"
    echo "    -H str    connect to host (default $ALF_DEFAULT_HOST). Use 'auto' to get from SSH."
    echo "    -m str    mode"
    echo "    -p num    port number to use for connection (default $ALF_DEFAULT_PORT)"
    echo "    -v        verbose"
    echo "    -V        print version"
    echo "    -w        wait for file to be closed by Alpha"
    echo "    --        end of options"
    echo "Linespec format: +l1[.c1][,l2[.c2]])"
    echo "Examples:"
    echo "    $(basename $0) somefile"
    echo "    $(basename $0) -p 12345 -s 2.3,4.9 -V somefile"
    echo "    $(basename $0) -e macRoman somefile"
}

# Determine current user (support sudo)
eval ALF_USER_DIR=$(builtin printf "~%q" "${SUDO_USER:-$LOGNAME}")

# Source configuration file (if any)
ALF_CONFIG_FILE=$ALF_USER_DIR/.ralpha.rc
if [ -e ${ALF_CONFIG_FILE} ] ; then
	status_log "Sourcing $ALF_CONFIG_FILE"
	source ${ALF_CONFIG_FILE}
fi

# Parse the command line options
while [[ $# -gt 0 ]]; do
case $1 in
	-c )
		ALF_NEW=1
	;;
	-d )
		ALF_DEBUG_INFO=1
	;;
	-e|-enc )
		ALF_ENC="$2"
		shift
	;;
	-f )
		ALF_FORCE=1
	;;
	-h )
		echo ralpha "- version $VERSION_NUMBER" 
		echo "Edit a remote file in Alpha through TCP."
		echo "(c) 2016-`date "+%Y"`, Alpha Community"
		echo "This is part of the AlphaCocoa project:"
		echo "http://sourceforge.net/projects/alphacocoa"
		print_usage
		exit 0 ;;
	-H )
		ALF_HOST="$2"
		shift
	;;
	-m )
		ALF_MODE="$2"
		shift
	;;
	-p )
		ALF_PORT="$2"
		shift
	;;
	+* )
		if [[ "$1" =~ \+([[:digit:]]+)(\.([[:digit:]]+))?([-,:]([[:digit:]]+)(\.([[:digit:]]+))?)? ]] 
		then
			line1=${BASH_REMATCH[1]}
			col1=${BASH_REMATCH[3]}
			line2=${BASH_REMATCH[5]}
			col2=${BASH_REMATCH[7]}
			if [ "$col1" == "" ] ; then
				col1=0
			fi
			if [ "$line2" == "" ] ; then
				line2=$line1
				col2=$col1
			elif [ $line2 -lt $line1 ] ; then
				l=$line1
				line1=$line2
				line2=$l
			fi
			if [ "$col2" == "" ] ; then
				line2=$((++line2))
				col2=0
			fi
			ALF_SELECTION=$line1-$col1-$line2-$col2
			status_debug "selection: $ALF_SELECTION"
		else
			echo "wrong format for linespec. Should be '+l1[.c1][,l2[.c2]]'."
		fi
	;;
	-v )
		ALF_VERBOSE=1
	;;
	-V )
		echo $VERSION_NUMBER 
		exit 0 ;;
	-w|-wait )
		ALF_WAIT=1
	;;
	-- )
		shift
		break
	;;
    *)
		break
        # unknown option
    ;;
esac
shift
done

if [ $ALF_NEW == 1 ] ; then
	if [ $# -gt 0 ] ; then
		ALF_NEW_NAME="$1"
		shift
	fi
	if [ "$ALF_NEW_NAME" == "" ] ; then
		ALF_NEW_NAME="untitled"
	fi
fi

status_debug "debugging=$ALF_DEBUG_INFO"
status_debug " encoding=$ALF_ENC"
status_debug "    force=$ALF_FORCE"
status_debug "     host=$ALF_HOST"
status_debug "     mode=$ALF_MODE"
status_debug "      new=$ALF_NEW"
status_debug "     port=$ALF_PORT"
status_debug "  verbose=$ALF_VERBOSE"
status_debug "     wait=$ALF_WAIT"

if [ ${#@} -gt 1 ]; then
    echo "Too many arguments."
	exit 1
fi

# Determine hostname
status_debug "ssh_connection: $SSH_CONNECTION"
if [ $ALF_DEBUG_INFO == 1 ]; then
	echo "Remote network interface:"
	ifconfig | grep inet
fi

# If ALF_HOST was set to 'auto' use the information from the ssh 
# connection.
if [ "$ALF_HOST" == "auto" -a "$SSH_CONNECTION" != "" ]; then
    ALF_HOST=${SSH_CONNECTION%% *}
fi
# Otherwise use the environment variable RALPHA_HOST if it is defined or
# the default value. This may be overriden with the -H option.
if [ "$ALF_HOST" == "" ] ; then
	ALF_HOST="${RALPHA_HOST:-$ALF_DEFAULT_HOST}"
fi

# Use the environment variable RALPHA_PORT if it is defined or the
# hardwired value. This may be overriden with the -p option.
if [ "$ALF_PORT" == "" ] ; then
	ALF_PORT="${RALPHA_PORT:-$ALF_DEFAULT_PORT}"
fi

status_debug "host: $ALF_HOST"
status_debug "port: $ALF_PORT"

ALF_LAST_ARG="${!#}"

if [ "$ALF_LAST_ARG" == "-" ] ; then
	if [ $ALF_NEW == 0 ] ; then
		echo "reading from stdin requires the -c option"
		exit 1
	else 
		ALF_READ_STDIN=1
	fi
elif [ $ALF_NEW == 1 ] ; then
	if [ ${#@} == 1 ] ; then
		ALF_READ_STRING=$ALF_LAST_ARG
	fi
else
	if [ ${#@} == 1 ] ; then
		ALF_FILE_PATH=$ALF_LAST_ARG
	else
		echo "missing file argument"
		exit 1
	fi
fi

# Handle the -c option. Attempt to create a new file.
if [ $ALF_NEW == 1 ] ; then
	if [ -e $ALF_NEW_NAME ] ; then
		echo "$ALF_NEW_NAME already exists"
		exit 1
	else
		touch $ALF_NEW_NAME
		if [ $? != 0 ] ; then
			echo "can't create new file $ALF_NEW_NAME"
			exit 1
		else
			if [ ${ALF_NEW_NAME:0:1} == / ] ; then
				# Absolute path
				ALF_FILE_PATH=$ALF_NEW_NAME
			else
				# Relative path
				ALF_FILE_PATH=`pwd`/$ALF_NEW_NAME
			fi
		fi
	fi
fi

status_debug "file path: $ALF_FILE_PATH"

if [ "$ALF_FILE_PATH" == "" ]; then
    if [ $ALF_WAIT == 1 ]; then
        ALF_FILE_PATH='-'
    else
		# Variable '$-' contains the shell options. If 'i' is present, it is 
		# an interactive shell.
        case "$-" in
            *i*)
                print_usage
                exit 1
                ;;
            *)
                ALF_FILE_PATH='-'
                ;;
        esac
    fi
fi


if [ "$ALF_FILE_PATH" != "" ]; then
    if [ -d "$ALF_FILE_PATH" ]; then
        echo "$ALF_FILE_PATH is a directory."
        exit 1
    fi

    if [ -f "$ALF_FILE_PATH" ] && [ ! -w "$ALF_FILE_PATH" ]; then
        if [ $ALF_FORCE == 0 ]; then
            echo "File $ALF_FILE_PATH is not writable. Use -f to force opening."
            exit 1
        elif [ $ALF_VERBOSE == 1 ]; then
            status_log "File $ALF_FILE_PATH is not writable. Opening anyway."
        fi
    fi
fi


function send_to_alpha {
	echo -n "$1" 1>&3
    status_log "$1"
}


function handle_reply {
    local filename
	filename=$1
	status_debug "file name: $filename"
	
	# $REPLY is the default value for a 'read' command
	while read 0<&3; do
		if [ "$REPLY" == "" -o "$REPLY" == "$ALF_EOT" ]; then
			break
		fi
		if [ "$tmp" == "" ]; then
			tmp=`mktemp -t ralphaXXXX`
			if ! [ -f $tmp ] ; then
				echo "can't create temp file"
				exit 1
			fi
			status_debug "temp file: $tmp"
		fi
		echo "$REPLY" >> "$tmp" 2>/dev/null
	done
	if [ -f "$tmp" ] ; then
		if cmp -s "$tmp" "$filename"; then
			status_debug "no changes"
		else
			status_debug "copying changes"
			cat "$tmp" > "$filename"
			rm -f "$tmp"
		fi
	fi
	status_debug "exiting handle_reply"
}


# Connect to Alpha port
status_log "* Connecting to port /dev/tcp/$ALF_HOST/$ALF_PORT"
exec 3<> /dev/tcp/$ALF_HOST/$ALF_PORT

if [ $? != 0 ]; then
    echo "-> unable to connect to Alpha on $ALF_HOST:$ALF_PORT"
    exit 1
fi
status_debug "-> connection OK"


# Send the command
status_log "* Sending the command"
send_to_alpha "editRemoteFile \"$ALF_FILE_PATH\" \"$ALF_MODE\" \"$ALF_ENC\" \"$ALF_SELECTION\""


# Now send the data. This chunk is delimited by a START OF TEXT control 
# code (^B: 02) and an END OF TEXT control code (^C: 03).
if [ $ALF_READ_STDIN == 1 ]; then
    if [ -t 0 ]; then
		# True if file descriptor 0 is open and refers to a terminal
        echo "Reading from stdin, press ^D to stop"
    else
        status_log "Reading from stdin"
    fi
    data=`cat`
	echo -n $ALF_STARTTEXT 1>&3
	echo -n "$data" 1>&3
	echo -n $ALF_ENDTEXT 1>&3
elif [ -f "$ALF_FILE_PATH" ]; then
	status_log "* Sending START OF TEXT"
	echo -n $ALF_STARTTEXT 1>&3
	if [ "$ALF_READ_STRING" != "" ] ; then
		status_log "* Sending string"
		echo -n $ALF_READ_STRING 1>&3
	else
		status_log "* Sending file's contents"
		cat "$ALF_FILE_PATH" 1>&3
	fi
	status_log "* Sending END OF TEXT"
	echo -n $ALF_ENDTEXT 1>&3
fi


# Signal the end by an END OF TRANSMISSION control code (^D: 04)
status_log "* Sending END OF TRANSMISSION"
echo -n $ALF_EOT 1>&3


# Closing stdin and stdout. Leave stderr open.
exec </dev/null >/dev/null 
status_log "* Launching background subprocess"
handle_reply $ALF_FILE_PATH &
status_debug "child's PID: $!"
if [ $ALF_WAIT == 1 ]; then
	wait
fi

status_log "Done"

